package com.aceitunaproject.server.entity.unit;

import java.util.LinkedList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.aceitunaproject.server.AceitunaContext;
import com.aceitunaproject.server.cargo.CargoContainer;
import com.aceitunaproject.server.entity.Loyalty;
import com.aceitunaproject.server.entity.PeopleContainer;
import com.aceitunaproject.server.equipment.Equipment;
import com.aceitunaproject.server.materialgoods.MaterialGood;
import com.aceitunaproject.server.player.Player;
import com.aceitunaproject.server.specialability.SpecialAbility;
import com.aceitunaproject.server.time.tick.TickManager;
import com.aceitunaproject.shared.exceptions.gamerules.IllegalActionException;
import com.aceitunaproject.shared.exceptions.gamerules.InsufficientCargoException;
import com.sun.sgs.app.AppContext;

/**
 * 
 * @author Federico Bergstein
 * 
 *         Represents an Unit
 */

public class Unit extends PeopleContainer implements CargoContainer {

	// TODO: All these constants are for testing purposes, they should came from
	// conf parameters and/or special methods
	private static final int MAX_TIME_UNITS = 5;
	private static final int INITIAL_TIME_UNITS = 5;
	private static final double TIME_UNITS_PER_TICK = 1;

	private static final long serialVersionUID = 1L;
	private int timeUnits;
	private double tickAtLastUpdate;
	private List<String> equipmentIDs = new LinkedList<String>();
	private Map<String, Long> cargo = new HashMap<String, Long>();
	private Long installationBeingBuilt = null;

	public Unit(long id, String name, Player owner, Player controller,
			double morale, int persons, Loyalty[] loyalties) {
		super(id, name, owner, controller, morale, persons, loyalties, null);
		tickAtLastUpdate = AceitunaContext.getTickManager().getCurrentTick();
		this.timeUnits = INITIAL_TIME_UNITS;

	}

	// TODO:remove
	public Unit(long id, String name, Player owner, Player controller) {
		super(id, name, owner, controller, 0, 1, new Loyalty[0], null);
		tickAtLastUpdate = AceitunaContext.getTickManager().getCurrentTick();
		this.timeUnits = INITIAL_TIME_UNITS;

	}

	public void addEquipment(Equipment equipment) {
		AppContext.getDataManager().markForUpdate(this);
		equipmentIDs.add(equipment.getName());
	}

	/**
	 * Sets the Unit TimeUnits
	 * 
	 * @param timeUnits
	 */
	public void setTimeUnits(int timeUnits) {
		AppContext.getDataManager().markForUpdate(this);
		this.timeUnits = timeUnits;
	}

	/**
	 * Adds TimeUnits to the Unit TimeUnits
	 * 
	 * @param timeUnits
	 *            to add
	 */
	public void addTimeUnits(int timeUnits) {
		AppContext.getDataManager().markForUpdate(this);
		this.timeUnits += timeUnits;
	}

	/**
	 * Subtracts TimeUnits from the Unit TimeUnits
	 * 
	 * @param timeUnits
	 *            to remove
	 */
	public void subtractTimeUnits(int timeUnits) {
		AppContext.getDataManager().markForUpdate(this);
		this.timeUnits -= timeUnits;
	}

	/**
	 * Return the TimeUnits the Unit has left.
	 * 
	 * @return the timeUnits
	 */
	public int getTimeUnits() {

		recalculateTimeUnits();

		return timeUnits;
	}

	@Override
	public boolean equals(Object anObject) {
		if (!super.equals(anObject)) {
			if (anObject instanceof Unit) {
				return this.getId() == ((Unit) anObject).getId();
			} else {
				return false;
			}
		} else {
			return true;
		}

	}

	/**
	 * updates the unit current time units, based on the number of ticks elapsed
	 * since the last time they were updated
	 */
	private void recalculateTimeUnits() {

		TickManager tickManager = AceitunaContext.getTickManager();

		double currentTick = tickManager.getCurrentTick();

		double tickDiff = currentTick - tickAtLastUpdate;

		// if the tick difference is 0 or less no change in the TUs is necessary
		if (tickDiff > 0) {

			// calculates the new amount of TUs
			double dblTimeUnits = timeUnits + tickDiff * TIME_UNITS_PER_TICK;

			int newTimeUnits = 0;
			// the new ammunt could be more than the max amount
			if (dblTimeUnits > MAX_TIME_UNITS) {
				// if so the new time units is the max amount
				newTimeUnits = MAX_TIME_UNITS;
			} else {
				// else, the new ammount is right
				newTimeUnits = (int) dblTimeUnits;
			}

			AppContext.getDataManager().markForUpdate(this);
			// updates the new time unit ammount
			timeUnits = newTimeUnits;
			tickAtLastUpdate = currentTick;
		}

	}

	public Map<String, Equipment> getEquipment() {

		return AceitunaContext.getEquipmentManager().getEquipmentCollection(
				equipmentIDs);

	}

	/**
	 * Returns whether or not the unit has a given Special Ability
	 * 
	 * @param ability
	 *            to check for
	 * @return true if it has it, false if it does not.
	 */
	public boolean HasAbility(SpecialAbility ability) {

		for (Equipment equipment : getEquipment().values()) {
			if (equipment.getSpecialAbilities().contains(ability)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Removes the unit from the game
	 */
	public void destroy() {
		// removes the unit id from the current map tile

		if (installationBeingBuilt != null) {
			AceitunaContext.getInstallationManager().getInstallation(installationBeingBuilt).setBuildingUnit(null);
		}
		AceitunaContext.getMapManager().getTile(this.getCurrentPosition())
				.removeUnit(this.getId());

		// removes the unit id from the player's unit lists.
		
		this.getController().removeUnit(this);
		this.getOwner().removeUnit(this);
		// removes from the object store.
		 AppContext.getDataManager().removeObject(this);

	}

	@Override
	public void addCargo(Player player, MaterialGood type, long blocks)
			throws IllegalActionException {

		long oldCargo = 0;

		if (cargo.containsKey(type.getDefaultName())) {
			oldCargo = cargo.get(type);
		}

		AppContext.getDataManager().markForUpdate(this);

		cargo.put(type.getDefaultName(), oldCargo + blocks);

		// TODO: Validate permissions, validate maximum cargo.

	}

	@Override
	public void retireCargo(Player player, MaterialGood type, long blocks)
			throws IllegalActionException {
		long oldCargo = 0;

		if (cargo.containsKey(type.getDefaultName())) {
			oldCargo = cargo.get(type.getDefaultName());
		}
		if (oldCargo - blocks >= 0) {
			AppContext.getDataManager().markForUpdate(this);
			cargo.put(type.getDefaultName(), oldCargo - blocks);
		} else {
			throw new InsufficientCargoException(type.getDefaultName(), this
					.getName());
		}

		// TODO: Validate permissions

	}

	/**
	 * @param installationBeingBuilt
	 *            the installationBeingBuilt to set
	 */
	public void setInstallationBeingBuilt(Long installationBeingBuilt) {
		AppContext.getDataManager().markForUpdate(this);
		this.installationBeingBuilt = installationBeingBuilt;
	}

	/**
	 * @return the installationBeingBuilt
	 */
	public Long getInstallationBeingBuilt() {
		return installationBeingBuilt;
	}

}
